#include <avr/io.h>
#include <avr/eeprom.h>
#include <avr/interrupt.h>
#include "util.h"
#include "main.h"
#include "pll.h"
#include "test.h"
#include <stdio.h>

#define BRR_19200 26

#define LED_ON LEDPORT |= _BV(LED)
#define LED_OFF LEDPORT &= ~_BV(LED)
uint8_t EEMEM max_programs=0, curr_program=0;
jammer_setting EEMEM *settings_ee;

uint8_t uart_init_done=0;
uint8_t debug=0;

/* Return the current battery voltage in mV (to prevent having to do 
   floating-point math */
uint16_t read_batt_voltage (void) {
  uint32_t i;
  uint32_t avg=0;
        
  ADMUX = 6; // low batt input
  for (i=0; i<2500; i++) {
    ADCSRA |=  _BV(ADSC);
    loop_until_bit_is_clear(ADCSRA, ADSC); // wait for conversion to finish;
    avg += ADC;
  }
  avg /= 235;
  
  return avg; 
}

static int uart_put(char c, FILE *foo) {
  if(!uart_init_done) uart_init(BRR_19200);
  uart_init_done=1;
  loop_until_bit_is_set(UCSR0A, UDRE0);
  NOP; NOP; NOP;
  UDR0 = c;
  if (c=='\n') return uart_put('\r', foo);
  return 0;
}

static FILE mystdout = FDEV_SETUP_STREAM(uart_put, NULL, _FDEV_SETUP_WRITE);

// What does this do?
SIGNAL (SIG_COMPARATOR) {
/*
	if (ACSR & _BV(ACO)) {
		LEDPORT |= _BV(LED);
	} else {
		LEDPORT &= ~_BV(LED);	
	}
*/
}

void init_varres(void) {
  SPICS_DDR |= _BV(SPICS);
  SPIDO_DDR |= _BV(SPIDO);
  SPICLK_DDR |= _BV(SPICLK);

  SPICS_PORT |= _BV(SPICS);
  SPICLK_PORT &= ~_BV(SPICLK);
}

uint8_t set_resistor(uint8_t rnum, uint8_t rval) {
  uint16_t d;
  if(debug) printf_P(PSTR("set_resistor(%d,%d)\r\n"), rnum, rval);

  d = (rnum << 8) | rval;

  SPICS_PORT &= ~_BV(SPICS);
  NOP; NOP; NOP; NOP;

  for (rnum=0; rnum < 10; rnum++) {
    if (d & 0x200) {
      SPIDO_PORT |= _BV(SPIDO);
    } else {
      SPIDO_PORT &= ~_BV(SPIDO);
    }
    SPICLK_PORT |= _BV(SPICLK);    
    NOP; NOP; NOP;
    NOP; NOP; NOP;
    SPICLK_PORT &= ~_BV(SPICLK); 
    d <<= 1;
  }
  NOP; NOP; NOP; NOP;
  SPICS_PORT |= _BV(SPICS);

  return 0;
}


void set_freq_high(void) {
  // set the pin to be a high output
  FREQSET_DDR |= _BV(FREQSET);
  FREQSET_PORT |= _BV(FREQSET);
}

void set_freq_low(void) {
  // set the pin to be a high input
  FREQSET_DDR &= ~_BV(FREQSET);
  FREQSET_PORT |= _BV(FREQSET);
}

void init_pwm(void) {
  DDRB |= _BV(PB1);  // make OCR1A it an output
  DDRB |= _BV(PB2);  // make OCR1B it an output
  
  TCCR1A = _BV(COM1A1) | _BV(COM1B1) | _BV(WGM12) | _BV(WGM10); // 8-10bit?
  TCCR1B = _BV(CS10);
  OCR1A = 0x0;  // change this value to adjust PWM.
  OCR1B = 0x0;  // change this value to adjust PWM.
}

void print_menu(void) {
  /* Printing the voltage isn't so useful here, because
     if you can read it then you're plugged into USB
     and charging -- but I don't know what else to do
     with it and it's not actually, you know, used anywhere else */
  printf_P(PSTR("Voltage: %d\n"), read_batt_voltage());  
  print_div();
  printf_P(PSTR(" p> Display progs\n"
		" a> Add prog\n"
		" d> Delete prog\n"
		" D> toggle debug mode\n"
		" t> Tune prog\n"
		" h> test high vco\n"
		" l> test low vco\n"
		" f> find freq for dc offset\n"
		" q> Quit (run)\n"));
  print_div();
  printf_P(PSTR("=> "));
}

void print_program(jammer_setting *setting, uint8_t n, uint8_t m) {
  print_div();
  printf_P(PSTR("Program #%d of %d\n"), n+1, m);
  
  printf_P(PSTR("VCO 1: %d -> %d (%d,%d)\r\n"), 
	   setting->startfreq1, setting->endfreq1, setting->dc_offset1, setting->bandwidth1); 
  printf_P(PSTR("VCO 2: %d -> %d (%d,%d)\r\n"), 
	   setting->startfreq2, setting->endfreq2, setting->dc_offset2, setting->bandwidth2); 
  
}

void delete_program(void) {
  uint8_t n, max;
  jammer_setting setting;
  
  display_programs();
  print_div();
  printf_P(PSTR("Delete #? "));
  n = uart_read16();
  max = eeprom_read_byte(&max_programs);
  if (n > max) {
    printf_P(PSTR("\nInvalid\n"));
    return;
  }
  
  // decrement stored number of programs
  eeprom_write_byte(&max_programs, max-1);
  if (max > 2 && n < max) {
    uint8_t i;
    // This doesn't work, but is better than what was here before? :(
    for(i = n; i < max; i++) {
      eeprom_read_block(&setting, 
			&settings_ee+sizeof(jammer_setting)*(i+1),
			sizeof(jammer_setting));
      eeprom_write_block(&setting, 
			 &settings_ee+sizeof(jammer_setting)*i,
			 sizeof(jammer_setting));
    }
  }
}

void tune_it (uint8_t n, uint8_t save) {
  jammer_setting setting;
  
  eeprom_read_block(&setting, 
		    &settings_ee+sizeof(jammer_setting)*n,
		    sizeof(jammer_setting));
  if ((setting.startfreq1 != 0) && (setting.endfreq1 != 0)) {
    setting.bandwidth1 = tune_vco_band(VCO_HI, setting.startfreq1, setting.endfreq1);
    setting.dc_offset1 = get_dcoff(VCO_HI);
  }	
  if ((setting.startfreq2 != 0) && (setting.endfreq2 != 0)) {
    setting.bandwidth2 = tune_vco_band(VCO_LOW, setting.startfreq2, setting.endfreq2);
    setting.dc_offset2 = get_dcoff(VCO_LOW);
  }
  if (save) {
    eeprom_write_block(&setting, 
		       &settings_ee+sizeof(jammer_setting)*n,
		       sizeof(jammer_setting));
  }
}


void tune_program(void) {
  uint8_t n;
  
  display_programs();
  print_div();
  printf_P(PSTR("Tune prog # "));
  n = uart_read16();
  if (n > eeprom_read_byte(&max_programs)) {
    printf_P(PSTR("\nInvalid #\n"));
  } else {
    tune_it(n-1, 1);
  }
}

void test_vco(uint8_t which) {
  //  uint16_t freq;
  if (debug) printf_P(PSTR("test_vco(%d)\n"), which);
  
  if (which==VCO_HI)
    printf_P(PSTR("Enter test freq for high VCO (1600-2500): "));
  else
    printf_P(PSTR("Enter test freq for low VCO (700-1700): "));
  
  //  freq = uart_read16();   fixme!
  //  printf_P(PSTR("\nFrequency step (MHz): "));
  //  test_pll2(which, freq);
  test_pll2(which);
}

void testdcoff(uint8_t which) {
  uint8_t dcoff;
  printf_P(PSTR("Enter OCR1%c value for %s VCO (5-254): "), 
	   which==VCO_LOW?'B':'A',
	   which==VCO_LOW?"lo":"hi");
  dcoff = uart_read16();
  //	printf_P(PSTR("\n\rDelay (ms): "));
  //	delay = uart_read16();
  
  find_freq_for_dc(which,dcoff);
}

void testbw(uint8_t which) {
  uint16_t center;
  printf_P(PSTR("Enter center frequency: "));
  center = uart_read16();
  test_bw(which, center);
}

void display_programs(void) {
  uint8_t i, progs;
  jammer_setting setting;
  
  progs = eeprom_read_byte(&max_programs);
  printf_P(PSTR("%d programs in memory\n"), progs);
  for (i=0; i< progs; i++) {
    eeprom_read_block(&setting, 
		      &settings_ee+sizeof(jammer_setting)*i,
		      sizeof(jammer_setting));
    print_program(&setting, i, progs);
  }
}

void add_program(void) {
  jammer_setting new_setting;
  uint8_t progs;
  
  printf_P(PSTR("VCO1 start: "));
  new_setting.startfreq1 = uart_read16();
  printf_P(PSTR("\nend: "));
  new_setting.endfreq1 = uart_read16();
  printf_P(PSTR("\nVCO2 start: "));
  new_setting.startfreq2 = uart_read16();
  printf_P(PSTR("\nend: "));
  new_setting.endfreq2 = uart_read16();
  new_setting.dc_offset1 = new_setting.dc_offset2 = 0;
  new_setting.bandwidth1 = new_setting.bandwidth2 = 0;
  
  progs = eeprom_read_byte(&max_programs);
  eeprom_write_block(&new_setting, 
		     &settings_ee+sizeof(jammer_setting)*progs, 
		     sizeof(jammer_setting));
  eeprom_write_byte(&max_programs, progs+1);
  printf_P(PSTR("\nProgram %d saved.\n"), progs);
  display_programs();
}

void run_menu(void) {
  char c=0;
  
  do {
    print_menu();
    c = uart_getchar();
    //    uart_putchar(c);
    uart_putchar('\r'); uart_putchar('\n');
    switch (c) {
    case 'p': display_programs(); break;
    case 'a': add_program(); break;
    case 'd': delete_program();	break;
    case 'D': debug=!debug; break;
    case 't': tune_program(); break;
    case 'h': test_pll2(VCO_HI); break;
    case 'l': test_pll2(VCO_LOW); break;
    case 'f': testbw(VCO_LOW); break;
    case 'g': testbw(VCO_HI); break;
    case 'q': break;
    default:
      printf_P(PSTR("Bad command\n"));
    }
  } while (c != 'q');
}

int main(void) {
  uint8_t i=0, progs;
  int programnum;
  jammer_setting setting;
  
  LEDDDR |= _BV(LED);
  LED_ON;
  //OSCCAL = 0xc0;
  //test_freq();    // alternate high/low sawtooth
  //test_led();   // test the LED only
  
  POWERCTL1_DDR |= _BV(POWERCTL1);
  POWERCTL2_DDR |= _BV(POWERCTL2);
  POWERCTL1_PORT &= ~_BV(POWERCTL1); // turn off vcos/gain
  POWERCTL2_PORT &= ~_BV(POWERCTL2); // turn off vcos/gain
  
  // initialize serial port
  uart_init(BRR_19200);
  stdout = &mystdout;
  // turn on bandgap ref, the comparator and allow an interrupt
  //ACSR = _BV(ACBG) | _BV(ACIE);
  //DIDR1 = _BV(AIN1D);
  //sei();
  
  set_freq_high();
  init_varres();
  init_pwm();
  pll_init();
  
  //test_resistors();      // test the digital potentiometers
  //test_DC();             // sweep DC offset (PWM)
  //test_powerswitch();    // turn power switches on & off
  //test_vcos();           // test VCOs: sweep and bandwidth
  //test_pll1();           // simple crystal/function test
  //test_uart();
  //test_osccal();
  //	test_pll2_sweep();
  printf_P(PSTR("Wavebubble v1.1 (program %d)\n"), MCUSR);
  MCUSR = 0;
  
  //test_pll2_rf();				// test PLL response to high VCO
  //test_pll2_if();				// test PLL response to low VCO
  
  /* hard-coded values for testing purposes
     OCR1A = 116;
     set_resistor(BANDWADJ1_RES, 88);
     POWERCTL1_PORT |= _BV(POWERCTL1); // turn on vcos/gain
     OCR1B = 54;
     set_resistor(BANDWADJ2_RES, 78);
     POWERCTL2_PORT |= _BV(POWERCTL2); // turn on vcos/gain
     
     progs = 1;  */
  
  progs = eeprom_read_byte(&max_programs);
  if  (progs != 0) {
    printf_P(PSTR("Press any key for menu!\n"));	
    delay_ms(1500);
  }
  if (uart_getch()) {
    uart_getchar();
    uart_putchar('\r'); uart_putchar('\n');
    run_menu();
  } else if (progs == 0)
    run_menu();
  
  progs = eeprom_read_byte(&max_programs);
  programnum = eeprom_read_byte(&curr_program);
  if (programnum >= progs)
    programnum = 0;
  
  eeprom_read_block(&setting, 
		    &settings_ee+sizeof(jammer_setting)*programnum,
		    sizeof(jammer_setting));
  uart_putchar('\r'); uart_putchar('\n');
  /*	setting.dc_offset1 = setting.dc_offset2 = 0;
	setting.bandwidth1 = setting.bandwidth2 = 0;
	setting.startfreq1=1900; setting.endfreq1=2000;
	setting.startfreq2=800; setting.endfreq2=900;
	eeprom_write_block(&setting, 
	&settings_ee,sizeof(jammer_setting));
  */
  print_program(&setting, programnum, progs);
  eeprom_write_byte(&curr_program, programnum+1);
  
	// blink to show the program;
  for (i = 0; i < programnum; i++) {
    LEDPORT |= _BV(LED);
    delay_ms(100);
    LEDPORT &= ~_BV(LED);
    delay_ms(100);
  }
  
  print_div();	
  
  if ((setting.dc_offset1 == 0) && (setting.bandwidth1 == 0) &&
      (setting.dc_offset2 == 0) && (setting.bandwidth2 == 0)) {
    tune_it(programnum, 0); // meme check return
  } else {
    if (setting.startfreq1 != 0) {
      set_dcoff(VCO_HI, setting.dc_offset1);
      set_resistor(VCO_HI, setting.bandwidth1);
      turn_on_vco(VCO_HI);
    }
    if (setting.startfreq2 != 0) {
      set_dcoff(VCO_LOW, setting.dc_offset2);
      set_resistor(VCO_LOW, setting.bandwidth2);
      turn_on_vco(VCO_LOW);
    }
  }
  
  printf_P(PSTR("Running program %d\n"), programnum);
  /* blink forever */
  while (1) {
    LEDPORT |= _BV(LED);
    delay_ms(100);
    LEDPORT &= ~_BV(LED);
    delay_ms(900);
  printf_P(PSTR("Voltage: %d\n"), read_batt_voltage());  
  }	
}
